package com.sunyard.pcc.core.service.impl;

import com.sunyard.pcc.common.bo.*;
import com.sunyard.pcc.common.constant.ErrorCodeEnum;
import com.sunyard.pcc.common.constant.IsDeletedEnum;
import com.sunyard.pcc.common.constant.IsSuperAdminEnum;
import com.sunyard.pcc.common.constant.UserStatusEnum;
import com.sunyard.pcc.common.exception.PccCommonException;
import com.sunyard.pcc.common.shiro.ShiroUtils;
import com.sunyard.pcc.common.validator.ValidationUtils;
import com.sunyard.pcc.core.manager.SysUserManager;
import com.sunyard.pcc.core.service.MenuService;
import com.sunyard.pcc.core.service.RoleService;
import com.sunyard.pcc.core.service.UserService;
import com.sunyard.pcc.dal.model.SysUserDO;
import com.sunyard.pcc.dal.query.SysUserQuery;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author: Hewei
 * @Description:
 * @Date: Created in 13:49 2018/2/23
 */
@Service(value = "userService")
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {

    @Autowired
    private SysUserManager sysUserManager;

    @Autowired
    private RoleService roleService;

    @Autowired
    private MenuService menuService;

    @Autowired
    private RedisTemplate redisTemplate;

    private static final int MAX_FAIL_COUNT = 5;

    /**
     * 用户账户失败锁定次数
     */
    private static final int LOCKED_COUNT = 10;

    /**
     * 根据电话号码（唯一）查询对应用户
     *
     * @param telephone 电话号码，唯一
     * @return
     * @throws PccCommonException
     */
    @Override
    public SysUserBO selectUserByTelephone(String telephone) throws PccCommonException {
        //参数不能为空
        Optional.ofNullable(telephone)
                .orElseThrow(() -> new PccCommonException(ErrorCodeEnum.PARAM_EMPTY));

        //查询对应用户
        SysUserQuery sysUserQuery = new SysUserQuery();
        sysUserQuery.createCriteria()
                .andTelephoneEqualTo(telephone)
                .andIsDeletedNotEqualTo(IsDeletedEnum.ISDELETED.getStatus());
        List<SysUserDO> sysUserDOList = sysUserManager.selectByQuery(sysUserQuery);
        if (sysUserDOList.size() == 0) {
            return null;
        } else if (sysUserDOList.size() != 1) {
            throw new PccCommonException(ErrorCodeEnum.DATABASE_DATA_ERROR);
        }

        SysUserBO sysUserBO = new SysUserBO();
        BeanUtils.copyProperties(sysUserDOList.get(0), sysUserBO);

        return sysUserBO;
    }


    /**
     * 添加新用户
     *
     * @param sysUserBO
     * @return
     * @throws PccCommonException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addNewUser(SysUserBO sysUserBO) throws PccCommonException {
        //非空校验
        ValidationUtils.isBlank(sysUserBO);

        //验证该账户是否已存在
        SysUserBO checkUserBO = selectUserByTelephone(sysUserBO.getTelephone());
        if (Objects.nonNull(checkUserBO)) {
            throw new PccCommonException(ErrorCodeEnum.USER_ALREADY_EXITS_ERROR);
        }

        //密码加密
        //随机盐
        String salt = RandomStringUtils.randomAlphanumeric(20);
        String newPassword = ShiroUtils.md5Encryption(sysUserBO.getPassword(), salt);

        //参数设置
        sysUserBO.setIsDeleted(IsDeletedEnum.UNDELETED.getStatus())
                .setStatus(UserStatusEnum.NORMAL.getCode())
                .setPassword(newPassword)
                .setSalt(salt)
                .setIsSuperAdmin(IsSuperAdminEnum.UNDELETED.getStatus())
                // TODO: 2018/2/27 设置添加用户
                .setCreateUserId(1l);

        log.info("添加新用户：{}" + sysUserBO);
        SysUserDO sysUserDO = new SysUserDO();
        BeanUtils.copyProperties(sysUserBO, sysUserDO);

        long count = sysUserManager.insertSelective(sysUserDO);
        if (count != 1l) {
            throw new PccCommonException(ErrorCodeEnum.INSERT_ERROR);
        }

        //用户角色权限列表
        List<Long> roleIds = sysUserBO.getRoleIdList();
        if (CollectionUtils.isEmpty(roleIds)) {
            return true;
        }

        //更新用户角色
        roleService.updateUserRoles(sysUserDO.getId(), roleIds);
        log.info("添加新用户成功");
        return true;

    }


    /**
     * 获取用户用户信息，及其相关权限，用于用户缓存
     *
     * @param telephone 用户手机号，用户唯一标识
     * @return
     * @throws PccCommonException
     */
    @Override
    public SysUserCacheBO selectUserAndRolesByTelephone(String telephone) throws PccCommonException {

        ValidationUtils.isBlank(telephone);

        SysUserCacheBO sysUserCacheBO = new SysUserCacheBO();

        SysUserBO sysUserBo = selectUserByTelephone(telephone);
        if (sysUserBo == null) {
            throw new PccCommonException(ErrorCodeEnum.USER_DO_NOT_EXITS);
        }
        BeanUtils.copyProperties(sysUserBo, sysUserCacheBO);

        Long userId = sysUserBo.getId();
        //获取用户对应权限
        List<SysRoleBO> sysRoleBOS = roleService.queryUserRoles(userId);

        //用户无对应角色，则无需查询其对应按钮列表
        if (CollectionUtils.isEmpty(sysRoleBOS)) {
            return sysUserCacheBO;
        }
        //查询用户具体菜单权限
        List<SysMenuBO> menuList = menuService.getUserMenuList(sysUserBo);

        sysUserCacheBO.setSysMenuBOS(menuList);

        return sysUserCacheBO;
    }

    /**
     * 用户登录失败次数限制及相关操作
     *
     * @param userName    用户名即用户电话号码
     * @param useRedis    是否使用redis
     * @param saveInRedis 用户登录信息是否存储在redis中
     * @throws PccCommonException
     */
    @Override
    public Boolean LoginLimitByConfig(String userName, Boolean useRedis, Boolean saveInRedis) throws PccCommonException {

        //非空校验
        ValidationUtils.isBlank(userName, useRedis, saveInRedis);

        if (useRedis && saveInRedis) {
            //使用redis进行存储操作
            return LoginLimitByRedis(userName);
        } else {
            //使用数据库进行操作
            return LoginLimitByDatabase(userName);
        }

    }


    /**
     * 重置用户登录失败次数
     *
     * @param userName    用户名即电话号码
     * @param useRedis    使用redis
     * @param saveInRedis 用redis存储用户登录失败信息
     * @throws PccCommonException
     */
    @Override
    public void resetFailureNum(String userName, Boolean useRedis, Boolean saveInRedis) throws PccCommonException {
        ValidationUtils.isBlank(userName, useRedis, saveInRedis);

        if (useRedis && saveInRedis) {
            resetFailureNumByRedis(userName);
        } else {
            resetFailureNumByDatabase(userName);
        }
    }



    /**
     * 使用 redis 管理用户登录失败次数限制
     *
     * @param userName
     */
    private Boolean LoginLimitByRedis(String userName) throws PccCommonException {

        SysUserLoginLimitBO sysUserLoginLimitBO = (SysUserLoginLimitBO) redisTemplate.opsForValue().get(userName);

        //该用户首次登录
        if (sysUserLoginLimitBO == null) {
            log.info("用户{}为首次登录，不进行错误登录校验", userName);
            sysUserLoginLimitBO = new SysUserLoginLimitBO();
            sysUserLoginLimitBO.setLoginCount(new AtomicInteger(1));
            sysUserLoginLimitBO.setLastLoginTime(LocalDateTime.now());
            sysUserLoginLimitBO.setUserStatus(UserStatusEnum.NORMAL.getCode());
            redisTemplate.opsForValue().set(userName, sysUserLoginLimitBO);
            return true;
        }

        Integer status = sysUserLoginLimitBO.getUserStatus();

        log.info("当前登录用户状态为{}", UserStatusEnum.getEnumByCode(status).getMessage());

        LocalDateTime endTime = LocalDateTime.now();
        Duration duration = Duration.between(sysUserLoginLimitBO.getLastLoginTime(), endTime);
        //用户被禁用
        if (UserStatusEnum.DELETED.getCode().equals(status)) {
            //用户被禁十分钟后进行解锁， 未到10分钟报错
            if (duration.toMinutes() < 10L) {
                sysUserLoginLimitBO.setLastLoginTime(endTime);
                redisTemplate.opsForValue().set(userName, sysUserLoginLimitBO);
                throw new PccCommonException(ErrorCodeEnum.USER_IS_DISABLED);
            } else {
                sysUserLoginLimitBO.setUserStatus(UserStatusEnum.NORMAL.getCode());//用户解锁
            }
        }

        //更新登录时间
        sysUserLoginLimitBO.setLastLoginTime(endTime);

        if (duration.toMinutes() > 5L) {
            sysUserLoginLimitBO.setLoginCount(new AtomicInteger(0));//重置登录失败次数
            redisTemplate.opsForValue().set(userName, sysUserLoginLimitBO);
            return true;
        } else {
            //两次登录时间差小于5分钟，验证错误登录次数
            int loginErrorCount = sysUserLoginLimitBO.getLoginCount().incrementAndGet();
            if (loginErrorCount > LOCKED_COUNT) {
                //错误登录超过十次
                sysUserLoginLimitBO.setUserStatus(UserStatusEnum.DELETED.getCode());
                redisTemplate.opsForValue().set(userName, sysUserLoginLimitBO);
                throw new PccCommonException(ErrorCodeEnum.USER_IS_DISABLED);
            } else if (loginErrorCount > MAX_FAIL_COUNT) {
                redisTemplate.opsForValue().set(userName, sysUserLoginLimitBO);
                throw new PccCommonException(ErrorCodeEnum.LOGIN_COUNT_LIMIT_ERROR);
            } else {
                //更新用户登录次数
                redisTemplate.opsForValue().set(userName, sysUserLoginLimitBO);
                return true;
            }
        }
    }


    /**
     * 使用数据库管理用户登录失败次数限制
     *
     * @param userName
     * @return
     */
    private Boolean LoginLimitByDatabase(String userName) throws PccCommonException {

        SysUserBO userBO = selectUserByTelephone(userName);
        //用户不存在
        if (userBO == null) {
            throw new PccCommonException(ErrorCodeEnum.USER_DO_NOT_EXITS);
        }

        Integer status = userBO.getStatus();
        log.info("当前登录用户状态为{}", UserStatusEnum.getEnumByCode(status).getMessage());

        LocalDateTime endTime = LocalDateTime.now();
        //用户登录时间校验
        Duration duration = Duration.between(userBO.getLoginDate() == null ? endTime : userBO.getLoginDate(), endTime);

        SysUserDO sysUserDO = new SysUserDO();
//            BeanUtils.copyProperties(userBO, sysUserDO);
        sysUserDO.setId(userBO.getId());
        //更新登录时间
        sysUserDO.setLoginDate(endTime);

        //用户被禁用
        if (UserStatusEnum.DELETED.getCode().equals(status)) {
            //距离上次登录时间不超过
            if (duration.toMinutes() < 10L) {
                //登录时间更新
                sysUserManager.updateByPrimaryKeySelective(sysUserDO);
                throw new PccCommonException(ErrorCodeEnum.USER_IS_DISABLED);
            } else {
                //用户禁用时间超过10m
                sysUserDO.setStatus(UserStatusEnum.NORMAL.getCode());
            }
        }


        if (duration.toMinutes() > 5L) {
            //重置登录失败次数
            sysUserDO.setFailCount(1);
            //更新最后登录时间
            sysUserManager.updateByPrimaryKeySelective(sysUserDO);
            return true;
        } else {
            //两次登录时间差小于5分钟，验证错误登录次数
            int loginErrorCount = userBO.getFailCount();
            sysUserDO.setFailCount(++loginErrorCount);
            if (loginErrorCount > LOCKED_COUNT) {
                //错误登录超过十次
                sysUserDO.setStatus(UserStatusEnum.DELETED.getCode());
                sysUserManager.updateByPrimaryKeySelective(sysUserDO);
                throw new PccCommonException(ErrorCodeEnum.USER_IS_DISABLED);
            } else if (loginErrorCount > MAX_FAIL_COUNT) {
                //登录次数超过五次
                sysUserManager.updateByPrimaryKeySelective(sysUserDO);
                throw new PccCommonException(ErrorCodeEnum.LOGIN_COUNT_LIMIT_ERROR);
            } else {
                //更新用户登录次数
                sysUserManager.updateByPrimaryKeySelective(sysUserDO);
                return true;
            }
        }

    }


    /**
     * 使用redis重置用户登录次数
     *
     * @param userName
     */
    private void resetFailureNumByRedis(String userName) throws PccCommonException {

        SysUserLoginLimitBO sysUserLoginLimitBO = (SysUserLoginLimitBO) redisTemplate.opsForValue().get(userName);

        if (sysUserLoginLimitBO == null) {
            throw new PccCommonException(ErrorCodeEnum.SYS_ERROR);
        } else {
            sysUserLoginLimitBO.setLastLoginTime(LocalDateTime.now());
            sysUserLoginLimitBO.setLoginCount(new AtomicInteger(0));
            redisTemplate.opsForValue().set(userName, sysUserLoginLimitBO);
        }

    }

    /**
     * 使用database重置用户登录次数
     *
     * @param userName 用户名 即手机号
     */
    private void resetFailureNumByDatabase(String userName) throws PccCommonException {

        SysUserQuery sysUserQuery = new SysUserQuery();
        sysUserQuery.createCriteria()
                .andTelephoneEqualTo(userName);

        SysUserDO sysUserDO = new SysUserDO();
        sysUserDO.setFailCount(0);
        sysUserDO.setLoginDate(LocalDateTime.now());
        sysUserManager.updateByQuerySelective(sysUserDO, sysUserQuery);

    }

}
